<?php
declare(strict_types=1);

namespace app\shop\library;

use app\common\facade\Token;
use app\shop\enums\user\UserStatus;
use app\shop\exception\AuthException;
use app\shop\model\user\UserModel;
use ba\Random;
use Throwable;

/**
 * 商城用户
 * @property int $id 用户ID
 */
class ShopAuth
{
    /**
     * token 入库 type
     */
    public const TOKEN_TYPE = 'joyshop';

    /**
     * refreshToken 入库 type
     */
    public const REFRESH_TOKEN_TYPE = self::TOKEN_TYPE . '-refresh';

    /**
     * 是否登录
     * @var bool
     */
    protected bool $loginEd = false;

    /**
     * 错误消息
     * @var string
     */
    protected string $error = '';

    /**
     * Model实例
     * @var ?UserModel
     */
    protected ?UserModel $model = null;

    /**
     * 令牌
     * @var string
     */
    protected string $token = '';

    /**
     * 刷新令牌
     * @var string
     */
    protected string $refreshToken = '';

    /**
     * 令牌默认有效期
     * 可在 config/buildadmin.php 内修改默认值
     * @var int
     */
    protected int $keepTime = 7200;

    /**
     * 刷新令牌有效期
     * @var int
     */
    protected int $refreshTokenKeepTime = 60 * 60 * 24 * 30;

    /**
     * 允许输出的字段
     * @var array
     */
    protected array $allowFields = [
        'id', 'username', 'nickname', 'avatar', 'gender', 'birthday', 'money', 'score', 'mobile','pid'
    ];

    public function __construct(array $config = [])
    {
        $store  = config('buildadmin.token.default');
        $config = config('buildadmin.token.stores.' . $store);

        if (isset($config['expire'])) {
            $this->setKeepTime($config['expire']);
        }
    }

    /**
     * 魔术方法-会员信息字段
     * @param $name
     * @return mixed 字段信息
     */
    public function __get($name): mixed
    {
        return $this->model?->$name;
    }

    /**
     * 初始化
     * @access public
     * @param array $options 传递给 /ba/Auth 的参数
     * @return ShopAuth
     */
    public static function instance(array $options = []): ShopAuth
    {
        $request = request();
        if (!isset($request->shopAuth)) {
            $request->shopAuth = new static($options);
        }
        return $request->shopAuth;
    }

    /**
     * 根据Token初始化会员登录态
     * @param $token
     * @return bool
     * @throws Throwable
     */
    public function init($token): bool
    {
        $tokenData = Token::get($token);
        if ($tokenData) {
            /**
             * 过期检查，过期则抛出 @see TokenExpirationException
             */
            Token::tokenExpirationCheck($tokenData);

            $userId = intval($tokenData['user_id']);
            if ($tokenData['type'] == self::TOKEN_TYPE && $userId > 0) {
                $this->model = UserModel::where('id', $userId)->findOrEmpty();
                if ($this->check()) {
                    $this->token = $token;
                    $this->loginSuccessful();
                    return true;
                }
            }
        }

        $this->setError('Token login failed');
        $this->reset();
        return false;
    }

    /**
     * 校验账号状态
     * @return bool
     */
    public function check(): bool
    {
        if (!$this->model || $this->model->isEmpty()) {
            $this->setError('账号不存在');
            return false;
        }
        if ($this->model->status != UserStatus::ENABLE) {
            $this->setError('账号异常已被限制登录');
            return false;
        }

        return true;
    }

    /**
     * 登录成功
     * @return bool
     * @throws AuthException
     */
    public function loginSuccessful(): bool
    {
        if (!$this->model) {
            throw new AuthException('缺少用户信息,请重新登录');
        }
        $this->model->startTrans();
        try {
            $this->model->login_failure   = 0;
            $this->model->last_login_time = time();
            $this->model->last_login_ip   = request()->ip();
            $this->model->save();
            $this->loginEd = true;

            if (empty($this->token)) {
                $this->setToken();
            }

            $this->model->commit();
        } catch (Throwable $e) {
            $this->model->rollback();
            $this->setError($e->getMessage());
            return false;
        }

        return true;
    }

    /**
     * 登录失败
     * @return bool
     * @throws AuthException
     */
    public function loginFailed(): bool
    {
        if (!$this->model) {
            throw new AuthException();
        }
        $this->model->startTrans();
        try {
            $this->model->login_failure++;
            $this->model->last_login_time = time();
            $this->model->last_login_ip   = request()->ip();
            $this->model->save();
            $this->model->commit();
        } catch (Throwable $e) {
            $this->model->rollback();
            $this->setError($e->getMessage());
            return false;
        }
        return $this->reset();
    }

    /**
     * 退出登录
     * @return bool
     */
    public function logout(): bool
    {
        $refreshToken = request()->post('refresh_token');
        if ($refreshToken) {
            Token::delete($refreshToken);
        }
        return $this->reset();
    }


    /**
     * 是否登录
     * @return bool
     */
    public function isLogin(): bool
    {
        return $this->loginEd;
    }

    /**
     * 获取用户模型
     * @return UserModel|null
     */
    public function getUser(): ?UserModel
    {
        return $this->model;
    }

    /**
     * 获取会员Token
     * @return string
     */
    public function getToken(): string
    {
        return $this->token;
    }

    /**
     * 获取会员刷新Token
     * @return string
     */
    public function getRefreshToken(): string
    {
        return $this->refreshToken;
    }

    /**
     * 设置Token
     * @return void
     */
    public function setToken(): void
    {
        if (config('buildadmin.user_sso')) {
            Token::clear(self::TOKEN_TYPE, $this->model->id);
            Token::clear(self::REFRESH_TOKEN_TYPE, $this->model->id);
        }
        $this->token = Random::uuid();
        Token::set($this->token, self::TOKEN_TYPE, $this->model->id, $this->keepTime);
        $this->refreshToken = Random::uuid();
        Token::set($this->refreshToken, self::REFRESH_TOKEN_TYPE, $this->model->id, $this->refreshTokenKeepTime);
    }

    /**
     * 获取会员信息 - 只输出允许输出的字段
     * @return array
     */
    public function getUserInfo(): array
    {
        if (!$this->model) return [];
        $info = $this->model->toArray();

        return array_intersect_key($info, array_flip($this->getAllowFields()));
    }

    /**
     * 获取允许输出字段
     * @return array
     */
    public function getAllowFields(): array
    {
        return $this->allowFields;
    }

    /**
     * 设置允许输出字段
     * @param $fields
     * @return void
     */
    public function setAllowFields($fields): void
    {
        $this->allowFields = $fields;
    }

    /**
     * 设置Token有效期
     * @param int $keepTime
     * @return void
     */
    public function setKeepTime(int $keepTime = 0): void
    {
        $this->keepTime = $keepTime;
    }

    /**
     * 设置刷新Token有效期
     * @param int $keepTime
     * @return void
     */
    public function setRefreshTokenKeepTime(int $keepTime = 0): void
    {
        $this->refreshTokenKeepTime = $keepTime;
    }

    /**
     * 设置错误消息
     * @param string $error
     * @return ShopAuth
     */
    public function setError(string $error): ShopAuth
    {
        $this->error = $error;
        return $this;
    }

    /**
     * 获取错误消息
     * @return string
     */
    public function getError(): string
    {
        return $this->error ? __($this->error) : '';
    }

    /**
     * 属性重置（注销、登录失败、重新初始化等将单例数据销毁）
     */
    protected function reset(bool $deleteToken = true): bool
    {
        if ($deleteToken && $this->token) {
            Token::delete($this->token);
        }

        $this->token        = '';
        $this->loginEd      = false;
        $this->model        = null;
        $this->refreshToken = '';
        $this->setError('');

        return true;
    }

    public function getUserSourceClient(): array|string
    {
        return request()->header('source-client') ?: '';
    }
}